/*
 * This file is part of AudioStream.
 * Copyright (C) 2010 Justin Li and Jacob Wirth
 *
 *  AudioStream is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  AudioStream is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with AudioStream.  If not, see <http://www.gnu.org/licenses/>.
 *  
 */

package org.frustra.audiostream.mp3;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.ArrayList;

import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.Header;


public class MP3Loader {
	/**
	 * Input stream pointing to the MP3 file
	 */
	private PushbackInputStream inputStream;
	
	/**
	 * Maximum chunk size to load
	 */
	private final int size = 4096;
	
	/**
	 * Used for preloading segments
	 */
	private MP3Segment toReturn;
	
	/**
	 * Used to hold the segment being carried out
	 * c2da90d680dd0aa013498d603699d62b
	 */
	private MP3Segment carryOut;
	
	/**
	 * Song started state
	 */
	private boolean started = false;

	/**
	 * Song ended state
	 */
	private boolean songOver = false;

	/**
	 * Contains all ID3 headers
	 * Additional headers:
	 * ARTIST, TITLE
	 */
	public ID3Parser id3Data = null;
	
	/**
	 * Creates a new playable MP3 from a file
	 * 
	 * @param f The file to be loaded
	 * @throws IOException
	 */
	public MP3Loader(File f) throws IOException
	{
		carryOut = new MP3Segment(new byte[0], 0);
		inputStream = new PushbackInputStream(new FileInputStream(f));
		toReturn = constructNextSegment();

		id3Data = new ID3Parser(f);
	}
	
	/**
	 * @return The next segment to be streamed
	 */
	public MP3Segment next()
	{
		MP3Segment ret = toReturn;
		toReturn = constructNextSegment();
		return ret;
	}
	
	/**
	 * @return The next MP3Segment in the loaded MP3
	 */
	private MP3Segment constructNextSegment()
	{
		//System.out.println("constructNextSegment()");
		byte[] carryInData = this.carryOut.getData();
		long carryInTime = this.carryOut.getTime();
		ArrayList<MP3Segment> nextFrames = new ArrayList<MP3Segment>();
		int j = this.size - carryInData.length;
		int frameSize = carryInData.length;
		while (j > 0)
		{
			MP3Segment addMe = getNextFrame();
			
			if (addMe == null)
			{
				if (nextFrames.size() == 0)
				{
					if (started) songOver = true;
					return null; // Impossible to make packet
				}
				else
				{
					break; // Less than preferred size
				}
			}
			
			nextFrames.add(addMe);
			j = j - addMe.getSize();
			frameSize += addMe.getSize();
		}
		
		if (frameSize > size)
		{
			started = true;
			byte[] theData = new byte[size];
			long theTime = 0;
			int i = 0;
			if (carryInData != null)
			{
				theTime += carryInTime;
				System.arraycopy(carryInData, 0, theData, 0, carryInData.length);
				i = carryInData.length;
			}
			for (MP3Segment frame : nextFrames)
			{
				if (frame.getSize() <= this.size - i)
				{
					System.arraycopy(frame.getData(), 0, theData, i, frame.getSize());
					theTime += frame.getTime();
					i += frame.getSize();
				}
				else
				{
					System.arraycopy(frame.getData(), 0, theData, i, this.size - i);
					byte[] carryOutData = new byte[frame.getSize() - (this.size - i)];
					System.arraycopy(frame.getData(), this.size - i, carryOutData, 0, carryOutData.length);
					long carryOutTime = (long)(frame.getTime() * (carryOutData.length / (double)frame.getSize()));
					theTime += (frame.getTime() - carryOutTime);
					this.carryOut = new MP3Segment(carryOutData, carryOutTime);
				}
			}
			return new MP3Segment(theData,theTime);
		}
		else
		{
			byte[] theData = new byte[frameSize];
			long theTime = 0;
			int i = 0;
			if (carryInData != null)
			{
				theTime += carryInTime;
				System.arraycopy(carryInData, 0, theData, 0, carryInData.length);
				i = carryInData.length;
			}
			for (MP3Segment frame : nextFrames)
			{
				System.arraycopy(frame.getData(), 0, theData, i, frame.getSize());
				theTime += frame.getTime();
				i += frame.getSize();
			}
			return new MP3Segment(theData,theTime);
		}
	}
	
	/**
	 * @return <b>True</b> if there is data to be streamed
	 */
	public boolean hasNext()
	{
		return toReturn != null;
	}
	
	/**
	 * @return The next frame in the MP3 file
	 */
	private MP3Segment getNextFrame()
	{
		try {
			byte[] head = new byte[4];
			inputStream.read(head); // Read header
			
			InputStream bais = new ByteArrayInputStream(head);
			Bitstream bs = new Bitstream(bais);
			
			Header h = bs.readFrame();
			byte[] frame = new byte[4 + h.calculate_framesize()];
			
			for (int i = 0; i < 4; i++) {
				frame[i] = head[i];
			}
			
			// Rest of the frame
			try {
				inputStream.read(frame, 4, (frame.length-4));
			} catch (Exception e) {
				System.out.println(e);
			}
			return new MP3Segment(frame, (int)h.ms_per_frame());
		} catch (Exception e) {
			return null;
		}
	}
	
	/**
	 * @return The started flag
	 */
	public boolean isStarted()
	{
		return started;
	}

	/**
	 * @return The songOver flag
	 */
	public boolean isFinished()
	{
		return songOver;
	}
}
